package com.loadburn.heron.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.loadburn.heron.enums.CharsetEnum;
import com.loadburn.heron.exceptions.ConfigLoaderException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletContext;
import java.io.*;
import java.net.URL;

/**
 * @author slacrey (scstlinfeng@yahoo.com)
 *         Date: 13-12-12
 */
public class ResourcesLoader {

    private static final Logger log = LoggerFactory.getLogger(ResourcesLoader.class);

    public static final String CLASSPATH_PREFIX = "classpath:";
    public static final String JAR_PREFIX = "jar:";
    public static final String URL_PREFIX = "url:";
    public static final String FILE_PREFIX = "file:";
    public static final String WEB_PREFIX = "web:";

    private ServletContext context;
    private ObjectMapper mapper = new ObjectMapper();
    private static ResourcesLoader ourInstance = null;

    private static synchronized void syncInit(ServletContext context) {
        if (ourInstance == null) {
            ourInstance = context == null ? new ResourcesLoader(null) : new ResourcesLoader(context);
        }
    }

    public static ResourcesLoader newInstance(ServletContext context) {
        if (ourInstance == null) {
            syncInit(context);
        }
        return ourInstance;
    }

    public static ResourcesLoader newInstance() {
        if (ourInstance == null) {
            syncInit(null);
        }
        return ourInstance;
    }

    public ResourcesLoader(ServletContext context) {
        this.context = context;
    }

    public String loadConfig(String path) {

        try {
            InputStream stream = getInputStreamForPath(path);
            return read(stream);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;

    }

    /**
     * 转换json字符串为对象
     *
     * @param json  字符串
     * @param clazz
     * @param <T>
     * @return
     * @throws IOException
     */
    public <T> T convertJsonToObject(String json, Class<T> clazz) throws IOException {
        return mapper.readValue(json, clazz);
    }

    // ==============================================================

    @SuppressWarnings({"UnusedDeclaration"})
    public static boolean hasResourcePrefix(String resourcePath) {
        return resourcePath != null &&
                (resourcePath.startsWith(CLASSPATH_PREFIX) ||
                        resourcePath.startsWith(URL_PREFIX) ||
                        resourcePath.startsWith(FILE_PREFIX) ||
                        resourcePath.startsWith(JAR_PREFIX) ||
                        resourcePath.startsWith(WEB_PREFIX));
    }

    public boolean resourceExists(String resourcePath) {
        InputStream stream = null;
        boolean exists = false;

        try {
            stream = getInputStreamForPath(resourcePath);
            exists = true;
        } catch (ConfigLoaderException e) {
            stream = null;
        } finally {
            if (stream != null) {
                try {
                    stream.close();
                } catch (IOException ignored) {
                }
            }
        }

        return exists;
    }


    public InputStream getInputStreamForPath(String resourcePath) {

        InputStream is;
        try {
            if (resourcePath.startsWith(CLASSPATH_PREFIX)) {
                is = loadFromClassPath(stripPrefix(resourcePath));

            } else if (resourcePath.startsWith(JAR_PREFIX)) {
                is = loadFromJar(stripPrefix(resourcePath));

            } else if (resourcePath.startsWith(URL_PREFIX)) {
                is = loadFromUrl(stripPrefix(resourcePath));

            } else if (resourcePath.startsWith(FILE_PREFIX)) {
                is = loadFromFile(stripPrefix(resourcePath));

            } else if (resourcePath.startsWith(WEB_PREFIX)) {
                is = loadFromWeb(stripPrefix(resourcePath), context);

            } else {
                is = loadFromFile(resourcePath);
            }

            if (null == is) {
                String message = String.format("Resource [%s] could not be found.", resourcePath);
                throw new ConfigLoaderException(message);
            }
        } catch (IOException e) {
            String message = String.format("Resource [%s] could not be found.", resourcePath);
            throw new ConfigLoaderException(message);
        }
        return is;
    }

    private static InputStream loadFromFile(String path) throws IOException {
        if (log.isDebugEnabled()) {
            log.debug("Opening file [" + path + "]...");
        }
        return new FileInputStream(path);
    }

    private static InputStream loadFromWeb(String path, ServletContext context) throws IOException {
        if (log.isDebugEnabled()) {
            log.debug("Opening file [" + path + "]...");
        }
        try {
            String pathName = context.getRealPath(path);
            return pathName == null ? null : new FileInputStream(pathName);
        } catch (FileNotFoundException e) {
            return null;
        }
    }

    private static InputStream loadFromUrl(String urlPath) throws IOException {
        log.debug("Opening url {}", urlPath);
        URL url = new URL(urlPath);
        return url.openStream();
    }

    private static InputStream loadFromClassPath(String path) {
        log.debug("Opening resource from class path [{}]", path);
        return ClassUtils.getResourceAsStream(path);
    }

    private static InputStream loadFromJar(String path) {
        log.debug("Opening resource from class path [{}]", path);
        return ClassUtils.getResourceAsStream(path);
    }

    private static String stripPrefix(String resourcePath) {
        return resourcePath.substring(resourcePath.indexOf(":") + 1);
    }

    /**
     * 从输入流中读取模板文件内容
     *
     * @param stream 输入流 {@link java.io.InputStream}
     * @return 模板内容
     * @throws java.io.IOException
     */
    public static String read(InputStream stream) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(stream, CharsetEnum.UTF8.getText()));
        StringBuilder builder = new StringBuilder();
        try {
            while (reader.ready()) {
                builder.append(reader.readLine());
                builder.append("\n");
            }
        } finally {
            close(stream);
        }
        return builder.toString();
    }

    public static void close(InputStream is) {
        if (is != null) {
            try {
                is.close();
            } catch (IOException e) {
                log.warn("Error closing input stream.", e);
            }
        }
    }

}
